package vip.ola.service.impl.code;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Service;
import org.apache.ibatis.annotations.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import vip.ola.core.common.Exception.ServiceException;
import vip.ola.core.common.constant.MchConstant;
import vip.ola.core.common.constant.RetEnum;
import vip.ola.core.common.util.MySeq;
import vip.ola.core.common.util.RedisUtil;
import vip.ola.core.common.vo.UserDayProfitVO;
import vip.ola.core.entity.code.CodeAccountHistory;
import vip.ola.core.entity.code.CodeUser;
import vip.ola.core.service.code.CodeAccountHistoryService;
import vip.ola.core.service.code.CodeInfoService;
import vip.ola.core.service.code.CodeOrderService;
import vip.ola.core.service.code.CodeUserService;
import vip.ola.service.dao.mapper.code.CodeUserMapper;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static java.math.BigDecimal.ZERO;
import static vip.ola.core.common.constant.CacheKey.*;
import static vip.ola.core.common.constant.CodeConstant.*;
import static vip.ola.core.common.constant.RetEnum.CODE_USER_UPDATE_MONEY_ERROR;

/**
 * <p>
 * 码商用户表 服务实现类
 * </p>
 *
 * @author faker
 * @since 2020-03-25
 */
@Service(interfaceClass = CodeUserService.class, version = "1.0.0")
@Transactional
public class CodeUserServiceImpl extends ServiceImpl<CodeUserMapper, CodeUser> implements CodeUserService {

    private final Logger logger = LoggerFactory.getLogger(CodeUserServiceImpl.class);

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CodeInfoService codeInfoService;
    @Autowired
    private CodeAccountHistoryService codeAccountHistoryService;
    @Autowired
    private CodeOrderService codeOrderService;

    public boolean add(CodeUser codeUser) {
        int row = baseMapper.insertSelective(codeUser);
        CodeUser updateUser = new CodeUser();
        updateUser.setId(codeUser.getId());
        if (CODE_CODE_TYPE.equals(codeUser.getType())) {
            redisUtil.delete(CODE_BUS_TYPE + CODE_CODE_TYPE + ":ALL");
        }
        if (CODE_CODE_TYPE.equals(codeUser.getType())) {
            updateUser.setCodeBusId(codeUser.getId());
            return super.updateById(updateUser);
        } else if (CODE_AGENT_TYPE.equals(codeUser.getType())) {
            updateUser.setAgentId(codeUser.getId());
            return super.updateById(updateUser);
        }
        return SqlHelper.retBool(row);
    }

    @Override
    public Map count(int currentPage, int pageSize, CodeUser codeUser) {
        Page page = new Page(currentPage, pageSize);
        return baseMapper.count(page, codeUser);
    }

    @Override
    public IPage<CodeUser> list(int currentPage, int pageSize, CodeUser codeUser) {
        Page page = new Page(currentPage, pageSize);
        Map<Long, UserDayProfitVO> today = codeOrderService.countSuccessByUser(codeUser.getCodeBusId(), LocalDate.now());
        Map<Long, UserDayProfitVO> yesterday = codeOrderService.countSuccessByUser(codeUser.getCodeBusId(), LocalDate.now().minusDays(1));
        IPage<CodeUser> pages = baseMapper.list(page, codeUser);
        if (codeUser.getCodeBusId() == null) {
            return pages;
        }
        for (CodeUser user : pages.getRecords()) {
            if (today != null && today.get(user.getId()) != null) {
                user.setTodayTotalNum(today.get(user.getId()).getTotalNum());
                BigDecimal todayProfit = Optional.ofNullable(today.get(user.getId()).getTotalProfit()).orElse(ZERO);
                user.setTodayTotalProfit(todayProfit);
            }
            if (yesterday != null && yesterday.get(user.getId()) != null) {
                user.setYesterdayTotalNum(yesterday.get(user.getId()).getTotalNum());
                BigDecimal yesterdayProfit = Optional.ofNullable(yesterday.get(user.getId()).getTotalProfit()).orElse(ZERO);
                user.setYesterdayTotalProfit(yesterdayProfit);
            }
        }
        return pages;
    }

    @Override
    public CodeUser getByCodeUser(CodeUser codeUser) {
        return baseMapper.getByCodeUser(codeUser);
    }

    public CodeUser getByName(String username) {
        return baseMapper.findByName(username);
    }

    @Override
    public CodeUser getCacheById(long id) {
        return redisUtil.get(CODE_USER + id, () -> baseMapper.selectById(id), CodeUser.class, HALF_HOURS);
    }

    @Override
    public void addCollAmount(long userId, BigDecimal amount, BigDecimal profit) {
        logger.info("更新账户金额, userId={}, amount={}, profit={}", userId, amount, profit);
        boolean isSuc = SqlHelper.retBool(baseMapper.addCollAmount(userId, amount, profit));
        if (!isSuc) {
            throw new ServiceException(CODE_USER_UPDATE_MONEY_ERROR);
        }
    }

    public boolean update(CodeUser codeUser) {
        LambdaQueryWrapper<CodeUser> wrapper = new LambdaQueryWrapper();
        wrapper.eq(CodeUser::getId, codeUser.getId()).select(CodeUser::getId, CodeUser::getAgentId, CodeUser::getCodeBusId, CodeUser::getType, CodeUser::getRate);
        CodeUser dbUser = getOne(wrapper);
        BigDecimal rate = codeUser.getRate();
        if (rate != null && rate.compareTo(dbUser.getRate()) != 0) {
            if (CODE_CODE_TYPE.equals(dbUser.getType())) {
                this.updateUserAgentRate(dbUser.getAgentId(), rate);
                codeInfoService.updateCodeAgentRate(dbUser.getAgentId(), rate);
            } else if (CODE_AGENT_TYPE.equals(dbUser.getType())) {
                this.updateUserBusRate(dbUser.getAgentId(), rate);
                codeInfoService.updateCodeBusRate(dbUser.getCodeBusId(), rate);
            }
        }
        boolean isSuc = updateById(codeUser);
        redisUtil.delete(CODE_USER + codeUser.getId());
        if (CODE_CODE_TYPE.equals(codeUser.getType())) {
            redisUtil.delete(CODE_BUS_TYPE + CODE_CODE_TYPE + ":ALL");
        }
        return isSuc;
    }

    public boolean cleanAmount(long userId) {
        LambdaUpdateWrapper<CodeUser> wrapper = new LambdaUpdateWrapper();
        wrapper.eq(CodeUser::getId, userId).set(CodeUser::getCanSettAmount, ZERO).set(CodeUser::getBalance, ZERO);
        return super.update(wrapper);
    }

    @Override
    public List<Map> getByUserType(int userType, Long codeBusId) {
        String codeStr = Optional.ofNullable(codeBusId).map(Objects::toString).orElse("ALL");
        return redisUtil.get(CODE_BUS_TYPE + userType + ":" + codeStr,
                () -> baseMapper.getByUserType(userType, codeBusId), List.class, ONE_HOURS);
    }

    @Override
    public int updateUserAgentRate(long agentId, BigDecimal rate) {
        return baseMapper.updateUserAgentRate(agentId, rate);
    }

    @Override
    public int updateUserBusRate(long codeBusId, BigDecimal rate) {
        return baseMapper.updateUserBusRate(codeBusId, rate);
    }

    @Override
    public int frozenAmount(long userId, BigDecimal amount) {
        return baseMapper.frozenAmount(userId, amount);
    }

    @Override
    public int updateSettAmount(long userId, int settStatus, BigDecimal amount) {
        return baseMapper.updateSettAmount(userId, settStatus, amount);
    }

    public boolean updateAmountById(long id, BigDecimal changeAmount, String bizItem, int fundDirection, String orderNo, String remark) {
        if (changeAmount.compareTo(ZERO) == 0) {
            // 更改金额为0, 不新增资金流水表, 无意义
            return false;
        }
        if (FUND_SUB.equals(fundDirection)) {
            changeAmount = ZERO.subtract(changeAmount);
        }
        CodeUser user = getById(id);
        CodeAccountHistory history = new CodeAccountHistory();
        history.setAgentId(user.getAgentId());
        if (CODE_AGENT_TYPE.equals(user.getType())) {
            history.setAgentId(user.getId());
        }
        history.setCodeBusId(user.getCodeBusId());
        if (CODE_CODE_TYPE.equals(user.getType())) {
            history.setCodeBusId(user.getId());
        }
        if (StringUtils.isNotBlank(orderNo)) {
            history.setOrderNo(orderNo);
        } else {
            history.setOrderNo(MySeq.getChangeAccount());
        }
        history.setUserId(user.getId()).setUsername(user.getUsername()).setBizType(TYPE_CHANGE_BALANCE).setBizItem(bizItem)
                .setFundDirection(fundDirection).setRemark(remark);

        CodeUser updateUser = new CodeUser();
        updateUser.setId(id);
        switch (bizItem) {
            case MchConstant.BIZ_ITEM_BALANCE:  // 账户余额
                updateUser.setBalance(changeAmount);
                history.setBeforeAmount(user.getBalance());
                history.setAmount(changeAmount);
                history.setAfterAmount(changeAmount.add(user.getBalance()));
                break;
            case MchConstant.BIZ_ITEM_CAN_SETT_MONEY: // 可结算余额
                updateUser.setCanSettAmount(changeAmount);
                history.setBeforeAmount(user.getCanSettAmount());
                history.setAmount(changeAmount);
                history.setAfterAmount(changeAmount.add(user.getCanSettAmount()));
                break;
            case MchConstant.BIZ_ITEM_FROZEN_MONEY: // 冻结金额
                updateUser.setFrozenAmount(changeAmount);
                history.setBeforeAmount(user.getFrozenAmount());
                history.setAmount(changeAmount);
                history.setAfterAmount(changeAmount.add(user.getFrozenAmount()));
                break;
            case MchConstant.BIZ_ITEM_SECURITY_MONEY:   // 保证金额
                updateUser.setDeposit(changeAmount);
                history.setBeforeAmount(user.getDeposit());
                history.setAmount(changeAmount);
                history.setAfterAmount(changeAmount.add(user.getDeposit()));
                break;
            default:
                throw new ServiceException(RetEnum.RET_SERVICE_ACCOUNT_TYPE_NOT_EXIST);
        }
        int row = baseMapper.updateAmountById(updateUser);
        if (row > 0) {
            return codeAccountHistoryService.save(history);
        }
        return false;
    }
}
